package cn.kuporo.seckill.order.application.service.impl;

import cn.kuporo.seckill.common.cache.distribute.DistributedCacheService;
import cn.kuporo.seckill.common.cache.local.LocalCacheService;
import cn.kuporo.seckill.common.cache.model.SeckillBusinessCache;
import cn.kuporo.seckill.common.constants.RedisConstants;
import cn.kuporo.seckill.common.exception.ErrorCode;
import cn.kuporo.seckill.common.exception.SeckillException;
import cn.kuporo.seckill.common.lock.DistributedLock;
import cn.kuporo.seckill.common.lock.factory.DistributedLockFactory;
import cn.kuporo.seckill.common.model.enums.PlaceOrderType;
import cn.kuporo.seckill.dubbo.interfaces.goods.SeckillGoodsDubboService;
import cn.kuporo.seckill.dubbo.interfaces.stock.SeckillStockDubboService;
import cn.kuporo.seckill.order.application.model.task.SeckillOrderTask;
import cn.kuporo.seckill.mq.MessageSenderService;
import cn.kuporo.seckill.order.application.service.PlaceOrderTaskService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: kuporo
 * @since: 2025/7/11
 * @description: 订单任务服务实现类
 */
@Service
@ConditionalOnProperty(name = "submit.order.type", havingValue = "async")
@Slf4j
@RequiredArgsConstructor
public class PlaceOrderTaskServiceImpl implements PlaceOrderTaskService {
    private final LocalCacheService<Long, Integer> localCacheService;
    private final DistributedCacheService distributedCacheService;
    private final DistributedLockFactory distributedLockFactory;
    private final MessageSenderService messageSenderService;

    @DubboReference
    private SeckillStockDubboService seckillStockDubboService;
    @DubboReference
    private SeckillGoodsDubboService seckillGoodsDubboService;

    @Value("${submit.order.token.multiple:1.5}")
    private Double multiple;
    @Value("${place.order.type:lua}")
    private String placeOrderType;
    // 可重入锁
    private Lock lock = new ReentrantLock();
    @Override
    public boolean submitOrderTask(SeckillOrderTask seckillOrderTask) {
        if (seckillOrderTask == null){
            throw new SeckillException(ErrorCode.PARAMS_INVALID);
        }
        String taskKey = RedisConstants.getKey(RedisConstants.ORDER_TASK_ID_KEY, seckillOrderTask.getOrderTaskId());
        // 检查是否是否执行过提交订单的方法，如果执行过提交订单，则抛出异常
        Long result = distributedCacheService.checkExecute(taskKey, RedisConstants.ORDER_TASK_EXPIRE_SECONDS);
        if (RedisConstants.CHECK_RECOVER_STOCK_HAS_EXECUTE.equals(result)){
            throw new SeckillException(ErrorCode.REDUNDANT_SUBMIT);
        }
        // 获取可用的下单许可
        Long goodsId = seckillOrderTask.getSeckillOrderCommand().getGoodsId();
        Integer availableOrderTokens = this.getAvailableOrderTokens(goodsId);
        // 不存在下单许可
        if (availableOrderTokens == null || availableOrderTokens <= 0){
            throw new SeckillException(ErrorCode.ORDER_TOKENS_NOT_AVAILABLE);
        }
        // 未获取到下单许可
        if (!this.takeOrderToken(goodsId)){
            log.info("submitOrderTask|获取下单许可失败|{},{}", seckillOrderTask.getUserId(), seckillOrderTask.getOrderTaskId());
            throw new SeckillException(ErrorCode.ORDER_TOKENS_NOT_AVAILABLE);
        }
         // 发送消息
        boolean sendSuccess = messageSenderService.send(seckillOrderTask);
        if (!sendSuccess){
            log.info("submitOrderTask|下单任务提交失败|{},{}", seckillOrderTask.getUserId(), seckillOrderTask.getOrderTaskId());
            // 恢复下单许可
            this.recoverOrderToken(goodsId);
            distributedCacheService.delete(taskKey);
        }
        return sendSuccess;
    }

    /**
     * 恢复下单许可
     */
    private boolean recoverOrderToken(Long goodsId) {
        Long result = distributedCacheService.recoverOrderToken(RedisConstants.getKey(RedisConstants.ORDER_TASK_AVAILABLE_TOKENS_KEY, goodsId));
        if (result == null){
            return false;
        }
        if (result == RedisConstants.LUA_RESULT_NOT_EXECUTE){
            this.refreshLatestAvailableTokens(goodsId);
            return true;
        }
        return result == RedisConstants.LUA_RESULT_EXECUTE_TOKEN_SUCCESS;
    }

    /**
     * 获取下单token，需要重点理解为何要循环三次处理业务
     */
    private boolean takeOrderToken(Long goodsId) {
        for (int i = 0; i < 3; i++) {
            Long result = distributedCacheService.takeOrderToken(RedisConstants.getKey(RedisConstants.ORDER_TASK_AVAILABLE_TOKENS_KEY, goodsId));
            if (result == null){
                return false;
            }
            if (RedisConstants.LUA_RESULT_NOT_EXECUTE == result){
                // 刷新远程缓存的token
                this.refreshLatestAvailableTokens(goodsId);
                continue;
            }
            return result == RedisConstants.LUA_RESULT_EXECUTE_TOKEN_SUCCESS;
        }
        return false;
    }

    /**
     * 获取可用的下单许可
     */
    private Integer getAvailableOrderTokens(Long goodsId) {
        Integer availableOrderTokens = localCacheService.getIfPresent(goodsId);
        if (availableOrderTokens != null){
            return availableOrderTokens;
        }
        return this.refreshLocalAvailableTokens(goodsId);
    }

    /**
     * 刷新本地缓存可用的下单许可，注意DoubleCheck
     */
    private Integer refreshLocalAvailableTokens(Long goodsId) {
        Integer availableOrderTokens = localCacheService.getIfPresent(goodsId);
        if (availableOrderTokens != null){
            return availableOrderTokens;
        }
        String availableTokensKey = RedisConstants.getKey(RedisConstants.ORDER_TASK_AVAILABLE_TOKENS_KEY, goodsId);
        Integer latestAvailableOrderTokens = distributedCacheService.getObject(availableTokensKey, Integer.class);
        if (latestAvailableOrderTokens != null){
            if (lock.tryLock()){
                try{
                    localCacheService.put(goodsId, latestAvailableOrderTokens);
                }finally {
                    lock.unlock();
                }
            }
            return latestAvailableOrderTokens;
        }
        return this.refreshLatestAvailableTokens(goodsId);
    }

    /**
     * 刷新分布式缓存的下单许可，double check
     */
    private Integer refreshLatestAvailableTokens(Long goodsId) {
        String lockKey = RedisConstants.getKey(RedisConstants.LOCK_REFRESH_LATEST_AVAILABLE_TOKENS_KEY, goodsId);
        DistributedLock distributedLock = distributedLockFactory.getDistributedLock(lockKey);
        try {
            boolean isLock = distributedLock.tryLock();
            if (!isLock){
                return null;
            }
            // 本地缓存已经存在数据
            Integer availableOrderTokens = localCacheService.getIfPresent(goodsId);
            if (availableOrderTokens != null){
                return availableOrderTokens;
            }
            //获取分布式缓存数据
            String availableTokensKey = RedisConstants.getKey(RedisConstants.ORDER_TASK_AVAILABLE_TOKENS_KEY, goodsId);
            Integer latestAvailableOrderTokens = distributedCacheService.getObject(availableTokensKey, Integer.class);
            //分布式缓存中存在数据，设置到本地缓存，并且返回数据
            if (latestAvailableOrderTokens != null){
                localCacheService.put(goodsId, latestAvailableOrderTokens);
                return latestAvailableOrderTokens;
            }
            
            /*//本地缓存和分布式缓存都没有数据，获取商品的库存数据
            String goodsKey = RedisConstants.getKey(RedisConstants.GOODS_ITEM_STOCK_KEY_PREFIX, goodsId);
            //获取商品库存
            Integer availableStock = distributedCacheService.getObject(goodsKey, Integer.class);
            if (availableStock == null || availableStock <= 0){
                return null;
            }*/
            
            // 分桶模式
            SeckillBusinessCache<Integer> availableStockCache = null;
            if (PlaceOrderType.BUCKET.getType().equals(placeOrderType)){
                availableStockCache = seckillStockDubboService.getAvailableStock(goodsId, 1L);
            }else {
                availableStockCache = seckillGoodsDubboService.getAvailableStock(goodsId, 1L);
            }
            if (availableStockCache == null || !availableStockCache.isExist() || availableStockCache.isRetryLater() || availableStockCache.getData() == null){
                return null;
            }
            Integer availableStock = availableStockCache.getData();
            //根据配置的比例计算下单许可
            latestAvailableOrderTokens = (int) Math.ceil(availableStock * multiple);
            distributedCacheService.put(availableTokensKey, latestAvailableOrderTokens, RedisConstants.ORDER_TASK_EXPIRE_SECONDS, TimeUnit.SECONDS);
            localCacheService.put(goodsId, latestAvailableOrderTokens);
            return latestAvailableOrderTokens;
        }catch (Exception e){
            log.error("refreshLatestAvailableTokens|刷新下单许可失败:{}", goodsId, e);

        }finally {
            distributedLock.unlock();

        }
        return null;
    }
}
